#include "reader.h"

byte readByte(reader_t *reader)
{
    byte result;

    result = reader->data[0];
    reader->data += 1;

    return result;
}

word readWord(reader_t *reader)
{
    word result;

    result = reader->data[0] | (reader->data[1] << 8) |
            (reader->data[2] << 16) | (reader->data[3] << 24);
    reader->data += 4;

    return result;
}

dword readDword(reader_t *reader)
{
    dword result;
    word low_res, high_res;

    low_res = readWord(reader);
    high_res = readWord(reader);
    result = low_res | (high_res << 32);

    return result;
}

int64 readLuaInt(reader_t *reader)
{
    return (int64) readDword(reader);
}

float64 readLuaNum(reader_t *reader)
{
    return (float64) readDword(reader);
}

byte *readString(reader_t *reader)
{
    size_t size;    

    size = (size_t) readByte(reader);

    if (size == 0) {            /* NULL string */
        return "";
    } else if ((byte) size == 0XFF) {  /* long string */
        size = (size_t) readDword(reader);
    }                           /* short string */

    return readBytes(reader, size - 1);
}

byte *readBytes(reader_t *reader, size_t n)
{
    byte *buf;

    buf = (byte *) malloc(n);
    if (buf == NULL) return NULL;
    memcpy(buf, reader->data, n);
    buf[n] = '\0';
    reader->data += n;

    return buf;
}

void checkHeader(reader_t *reader)
{
    if (strcmp(readBytes(reader, 4), LUA_SIGNATURE) != 0) 
        panic("not a precompiled chunk!");
    else if (readByte(reader) != LUAC_VERSION)
        panic("version mismatch!");
    else if (readByte(reader) != LUAC_FORMAT)
        panic("format mismatch!");
    else if (strcmp(readBytes(reader, 6), LUAC_DATA) != 0)
        panic("corrupted!");
    else if (readByte(reader) != CINT_SIZE)
        panic("int size mismatch!");
    else if (readByte(reader) != CSIZET_SIZE)
        panic("size_t size mismatch!");
    else if (readByte(reader) != INSTRUCTION_SIZE)
        panic("instruction size mismatch!");
    else if (readByte(reader) != LUA_INTEGER_SIZE)
        panic("lua_Integer size mismatch!");
    else if (readByte(reader) != LUA_NUMBER_SIZE)
        panic("lua_Number size mismatch!");
    else if (readLuaInt(reader) != LUAC_INT)
        panic("endianness mismatch");
    else if (readLuaNum(reader) != LUAC_NUM)
        panic("float format mismatch!");
}

void readProto(reader_t *reader, byte *parent_srcname, proto_t *proto)
{
    byte *buf;

    buf = readString(reader);
    if (strcmp(buf, "") == 0)
        strncpy(buf, parent_srcname, strlen(parent_srcname) + 1);

    strncpy(proto->srcname, buf, strlen(buf) + 1);
    proto->line_defined = readWord(reader);
    proto->last_line_defined = readWord(reader);
    proto->num_params = readByte(reader);
    proto->is_vararg = readByte(reader);
    proto->max_stack_size = readByte(reader);
    proto->instr_tbl = readInstr(reader);
    proto->const_tbl = readConst(reader);
}

word *readInstr(reader_t *reader)
{
    size_t instr_count;
    word *instr_tbl;

    instr_count = (word) readWord(reader);
    if (instr_count == 0) return NULL;

    instr_tbl = (word *) malloc(sizeof(word) * instr_count);
    if (instr_tbl == NULL) return NULL;
    memset(instr_tbl, 0, sizeof(word) * instr_count);

    for (int i = 0; i < instr_count; i++) 
        instr_tbl[i] = readWord(reader);

    return instr_tbl;
}

void *readConst(reader_t *reader)
{
    size_t const_count;
    const_t *const_tbl;
    byte tag;   /* distinguish the type of constant */

    const_count = (word) readWord(reader);
    if (const_count == 0) return NULL;

    const_tbl = (const_t *) malloc(sizeof(const_t) * const_count);
    if (const_tbl == NULL) return NULL;
    memset(const_tbl, 0, sizeof(const_t) * const_count);

    for (int i = 0; i < const_count; i++) {
        tag = readByte(reader);
        const_tbl[i].tag = tag;

        switch (tag) {
            case TAG_NIL: 
                const_tbl[i].nil_v = readByte(reader);          
                break;
            case TAG_BOOLEAN:   
                const_tbl[i].bool_v = (readByte(reader) != 0);  
                break;
            case TAG_INTEGER: 
                const_tbl[i].int_v = readLuaInt(reader);
                break;
            case TAG_NUMBER:
                const_tbl[i].num_v = readLuaNum(reader);
                break;
            case TAG_SHORT_STR:
                const_tbl[i].short_s = readString(reader);
                break;
            case TAG_LONG_STR:
                const_tbl[i].long_s = readString(reader);
                break;
            default:
                panic("corrupted!");
        }
    }
}